package jmine.tec.di.spring;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;

/**
 * Some utility methods for dealing with bean factories, like merging two {@link BeanFactory} or creating a new merged instance of
 * {@link BeanFactory}.
 * 
 * @author takeshi
 */
public final class BeanFactoryUtils {
    /**
     * 
     * 
     */
    private BeanFactoryUtils() {
    }

    /**
     * Merges two {@link ConfigurableListableBeanFactory} instances into one, either merging back to the first argument or creating another
     * instance, depending on the given argument.
     * 
     * @param <E> the type of the bean factory
     * @param root the root bean factory that will receive the other bean definition
     * @param other the bean factory to pull bean definitions from
     * @param mergeBack if true, the given root bean factory will be modified. If false, a new bean factory will be created and returned
     * @return {@link ConfigurableListableBeanFactory}
     */
    @SuppressWarnings("unchecked")
    public static <E extends BeanDefinitionRegistry & BeanFactory> E mergeBeanFactoryDefinition(final E root,
            final ConfigurableListableBeanFactory other, final boolean mergeBack) {
        if (mergeBack) {
            mergeBeanFactoryIntoRoot(root, other);
            return root;
        } else {
            return (E) createMergedBeanFactory(root, other);
        }
    }

    /**
     * Merges a {@link ConfigurableListableBeanFactory} into a root {@link ConfigurableListableBeanFactory}. Overriding of bean definitions
     * are defined by the given implementation of {@link BeanDefinitionRegistry}.
     * 
     * @param root {@link ConfigurableListableBeanFactory} the root bean factory to be modified
     * @param other the bean factory to pull bean definitions instances
     */
    public static void mergeBeanFactoryIntoRoot(final BeanDefinitionRegistry root,
            final ConfigurableListableBeanFactory other) {
        String[] defs = other.getBeanDefinitionNames();
        for (int i = 0; i < defs.length; i++) {
            String defName = defs[i];
            BeanDefinition def = other.getBeanDefinition(defName);
            root.registerBeanDefinition(defName, def);
        }
    }

    /**
     * Merges two bean factory definition into another instance of {@link BeanFactory} that haves the given root {@link BeanFactory} as a
     * parent. The returned {@link BeanFactory} can have its {@link BeanDefinition}'s overriden, therefore will not throw an exception if
     * two bean definitions have the same name.
     * 
     * @param root the root {@link BeanFactory}
     * @param other the {@link BeanFactory} to purge {@link BeanDefinition} from.
     * @return a new instance of {@link BeanFactory}
     */
    public static DefaultListableBeanFactory createMergedBeanFactory(final BeanFactory root,
            final ConfigurableListableBeanFactory other) {
        DefaultListableBeanFactory bf = new DefaultListableBeanFactory(root);
        bf.setAllowBeanDefinitionOverriding(true);
        mergeBeanFactoryIntoRoot(bf, other);
        return bf;
    }

}
